package org.xqh.study.google.guava.reflect;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.*;
import com.google.common.reflect.ClassPath.ClassInfo;
import com.google.common.reflect.ClassPath.ResourceInfo;
import org.junit.Test;

import java.io.IOException;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * 反射工具类
 * 1、TypeToken
 * 2、Invokable
 * 3、ClassPath
 * 4、Dynamic Proxies（动态代理）
 */
public class ReflectTest {

    //TypeToken类是用来帮我们解决java运行时泛型类型被擦除的问题的。
    @Test
    public void testTypeToken() {
        ArrayList<String> stringList = Lists.newArrayList();
        ArrayList<Integer> intList = Lists.newArrayList();
        System.out.println("intList type is " + intList.getClass());
        System.out.println("stringList type is " + stringList.getClass());

        // 认为stringList和intList的类型是一样的。这就是所谓的泛型类型擦除, 泛型String和Integer被檫除了。
        System.out.println(stringList.getClass().isAssignableFrom(intList.getClass()));

        System.out.println(/*-------------------------------------------我是分割线---------------------------------------------------*/);

        // 定义空的匿名类
        TypeToken<ArrayList<String>> stringTypeToken = new TypeToken<ArrayList<String>>() {
        };
        TypeToken<ArrayList<Integer>> intTypeToken = new TypeToken<ArrayList<Integer>>() {
        };

        // TypeToken解析出了泛型参数的具体类型
        TypeToken<?> genericTypeToken = stringTypeToken.resolveType(ArrayList.class.getTypeParameters()[0]);
        TypeToken<?> genericTypeToken2 = intTypeToken.resolveType(ArrayList.class.getTypeParameters()[0]);

        //返回泛型的类型
        System.out.println(genericTypeToken.getType());
        System.out.println(genericTypeToken2.getType());

        //返回该泛型的运行时类
        System.out.println(genericTypeToken2.getRawType());
        //检查该泛型是不是数组
        System.out.println(genericTypeToken2.isArray());
        //是否是原始类型（调用Class类中的isPrimitive()方法）
        System.out.println(genericTypeToken2.isPrimitive());
    }

    //Invokable是对java.lang.com.guava.test.reflect.Method和java.lang.com.guava.test.reflect.Constructor的流式包装。
    // 它简化了常见的反射代码的使用。
    @Test
    public void testInvokable() {

        class InvokableTestClass {
            public InvokableTestClass() {

            }
            public InvokableTestClass(int a) {

            }

            public int sum(int a, int b) throws NullPointerException {
                return a + b;
            }
        }

        // 创建对象
        InvokableTestClass object = new InvokableTestClass(10);
        // 获取对象对应的类
        Class clazz = object.getClass();
        //获取所有方法
        Method[] invokableSourceList = clazz.getMethods();
        //获取构造函数
        //Constructor[] invokableSourceList =  clazz.getConstructors();
        if (invokableSourceList.length > 0) {
            for (Method item : invokableSourceList) {
                System.out.println("======================我是分割线=====================");
                // 把Method包装成Invokable
                Invokable methodInvokable = Invokable.from(item);
                // 方法名字
                System.out.println("方法名字：" + methodInvokable.getName());

                // getDeclaringClass() 获取定义该方法的类
                System.out.println("定义该方法的类：" + methodInvokable.getDeclaringClass());

                // getOwnerType() 获取定义该方法的class的包装对象Type
                System.out.println("定义该方法的类的包装对象：" + methodInvokable.getOwnerType().getType());

                // isOverridable() 该方法是否可以重写
                System.out.println("是否可以重写：" + methodInvokable.isOverridable());

                // isVarArgs() 该方法是否可变参数
                System.out.println("是否可变参数：" + methodInvokable.isVarArgs());

                // getReturnType() 该方法返回值类型
                System.out.println("返回值类型：" + methodInvokable.getReturnType().getType());

                // getParameters() 获取参数
                ImmutableList<Parameter> parameterList = methodInvokable.getParameters();
                for (int index = 0; index < parameterList.size(); index++) {
                    System.out.println("方法参数" + index + ": " + parameterList.get(index).getType());
                }

                // getExceptionTypes() 获取异常类
                ImmutableList<TypeToken> exceptionList = methodInvokable.getExceptionTypes();
                for (int index = 0; index < exceptionList.size(); index++) {
                    System.out.println("异常类" + index + ": " + exceptionList.get(index).getType());
                }

                // 转换成jdk1.8的 AnnotatedType
                AnnotatedType annotatedType = methodInvokable.getAnnotatedReturnType();
                System.out.println("annotatedType: " + annotatedType.getType());
            }
        }
    }

    //ClassPath是一种实用工具，它提供尽最大努力的类路径扫描。
    //但是，ClassPath是一个尽力而为的工具。它只扫描jar文件中或者某个文件目录下的class文件。
    //也不能扫描非URLClassLoader的自定义class loader管理的class，所以不要将它用于关键任务生产任务。
    @Test
    public void testClassPath() throws IOException {
        /*
        创建一个ClassPath对象
        public static ClassPath from(ClassLoader classloader) throws IOException;

        返回从当前类路径可加载的所有资源，包括所有可加载类的类文件，但不包括“META-INF / MANIFEST.MF”文件
        public ImmutableSet<ResourceInfo> getResources();

        返回可加载的所有类
        public ImmutableSet<ClassInfo> getAllClasses();

        返回所有的顶级类（不包括内部类）
        public ImmutableSet<ClassInfo> getTopLevelClasses();

        返回指定package下的所有顶级类
        public ImmutableSet<ClassInfo> getTopLevelClasses(String packageName);

        返回所有指定package已经子package下所有的顶级类
        public ImmutableSet<ClassInfo> getTopLevelClassesRecursive(String packageName);
         */

        class ClassPathBase {

        }

        ClassPath classpath = ClassPath.from(ClassPathBase.class.getClassLoader());
        // 返回所有指定package下所有的顶级类
        ImmutableSet<ClassInfo> topClassList =  classpath.getTopLevelClassesRecursive("com/guava/test/reflect");
        if (topClassList != null && !topClassList.isEmpty()) {
            for (ClassPath.ClassInfo classInfoItem : topClassList) {
                System.out.println("------" + classInfoItem.toString());
            }
        }

        // 返回从当前类路径可加载的所有资源，包括所有可加载类的类文件，但不包括“META-INF / MANIFEST.MF”文件
        ImmutableSet<ResourceInfo> resourceList =  classpath.getResources();
        if (resourceList != null && !resourceList.isEmpty()) {
            for (ClassPath.ResourceInfo resourceItem : resourceList) {
                try{
                    System.out.println("======" + resourceItem.url().toString());
                }catch (Exception e) {
                    System.out.println(e.getStackTrace());
                }
            }
        }
    }

    //Reflection简单使用以及动态代理
    @Test
    public void testDynamicProxies() {

        //Reflection类简单使用
        // 获取ReflectTest类对应的包名字
        System.out.println(Reflection.getPackageName(ReflectTest.class));
        System.out.println(Reflection.getPackageName("com.guava.test.reflect.ReflectionTest"));

        System.out.println("===================================我是分割线==========================================");

        //JDK动态代理
        AddService service = new AddServiceImpl();
        JdkInvocationHandlerImpl<AddService> addServiceHandler = new JdkInvocationHandlerImpl<>(service);
        AddService proxy = addServiceHandler.getProxy();
        System.out.println(proxy.add(1, 2));

        System.out.println("===================================我是分割线==========================================");

        //guava动态代理
        //实用方法 Reflection.newProxy(Class, InvocationHandler)是一种更安全，更方便的 API。
        AddService service2 = new AddServiceImpl();
        GuavaInvocationHandlerImpl<AddService> addServiceHandler2 = new GuavaInvocationHandlerImpl<>(AddService.class, service2);
        AddService proxy2 = addServiceHandler2.getProxy();
        System.out.println(proxy2.add(1, 2));

        System.out.println("===================================我是分割线==========================================");

        //guava抽象动态代理
        //有时候你可能想动态代理能够更直观的支持 equals()，hashCode()和 toString()，那就是：
        //   1、一个代理实例 equal 另外一个代理实例，只要他们有同样的接口类型和 equal 的 invocation handlers。
        //   2、一个代理实例的 toString()会被代理到 invocation handler 的 toString()，这样更容易自定义。
        //AbstractInvocationHandler 实现了以上逻辑。
        //除此之外，AbstractInvocationHandler 确保传递给 handleInvocation(Object, Method, Object[]))的参数数组永远不会空，从而减少了空指针异常的机会。
        AddService service3 = new AddServiceImpl();
        GuavaAbstractInvocationHandlerImpl<AddService> addServiceHandler3 = new GuavaAbstractInvocationHandlerImpl<>(AddService.class, service3);
        AddService proxy3 = addServiceHandler3.getProxy();
        System.out.println(proxy3.add(1, 2));
    }
}